{--
    Examples for using record syntax copied from
    http://mmhelloworld.github.io/blog/2014/03/15/frege-record-accessors-and-mutators/
    -}
module examples.Records where

import Test.QuickCheck

data Point = Point {
    x :: Int,
    y :: Int
    }
derive Eq Point

q00 = Point {x=0, y=0}      -- ctor with named parameters
p00 = Point 0 0             -- ctor with positional parameters works but is discouraged since
                            --   it is unfriendly to refactoring/incremental development

ctorEquality = once (p00 == q00)

                            -- two alternatives of setting a record value
p10 = Point.{x =} p00 1     -- setting the x
p11 = p10.{y = 1}           -- setting the y

                            -- two alternatives of updating through a function
p21 = Point.{x <-} p11 (+1) -- updating the x
p22 = p21.{y <- (+1)}       -- updating the y

p33 = p22.{x <- (+1), y=3}  -- combinations of the above

matchXis1 :: Point -> Bool  -- constructor decomposition ..
matchXis1 (Point 1 _) = true  -- .. is possible but not desirable ..
matchXis1 (Point _ _) = false -- .. better use record match as seen below

checkMatchXis1 = once (matchXis1 p10)

matchYis1 :: Point -> Bool  -- record match
matchYis1 Point {y=1} = true   -- matching on a subset
matchYis1 Point {}    = false  -- matching everything else

checkMatchYis1 = once (matchYis1 p11)

matches11 :: Point -> Bool  -- a "record pun"
matches11 Point {x,y} =     -- x and y automatically extracted under the same name
    x == 1 && y == 1

checkMatches11 = once (matches11 p11)


data Circle = Circle {      -- used to show composition and nesting
    center :: Point,
    radius :: Int
    }
derive Eq Circle

c000 = Circle {center = p00, radius = 0}
c100 = c000.{center <- Point.{x = 1}}                       -- updating nested data
c110 = c100.{center <- _.{y = 1}}                           -- type can be derived
c111 = c100.{center <- Point.{y <- (+1)}, radius <- (+1)}   -- combinations of nesting


data DimPoint = Point2D { x :: Int, y :: Int}               -- used to check for z
              | Point3D { x :: Int, y :: Int, z :: Int}

p2D = Point2D {x=0, y=0}
p3D = Point3D {x=0, y=0, z=0}

originXasProperty    = once (p00.x       == 0)
originXasFunction    = once (Point.x p00 == 0)

p10state = once ( p10  == Point {x=1, y=0} )
p11state = once ( p11  == Point {x=1, y=1} )
p21state = once ( p21  == Point {x=2, y=1} )
p22state = once ( p22  == Point {x=2, y=2} )
p33state = once ( p33  == Point {x=3, y=3} )

c000CenterXasProperty     = once ( c000.center.x == 0 )
c000State   = once ( c000 == Circle {center = p00, radius = 0} )
c100State   = once ( c100 == Circle {center = p10, radius = 0} )
c110State   = once ( c110 == Circle {center = p11, radius = 0} )
c111State   = once ( c111 == Circle {center = p11, radius = 1} )

p2DhasNoZ   = once ( not $ DimPoint.{z?} p2D )
p3DhasZ     = once ( p3D.{z?} )